//   Copyright 2012,2013 Vaughn Vernon
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
package com.saasovation.identityaccess.resource;

import com.saasovation.common.annotation.TestContainers;
import com.saasovation.common.domain.model.DomainEventPublisher;
import com.saasovation.common.event.EventStore;
import com.saasovation.common.media.RepresentationReader;
import com.saasovation.common.serializer.ObjectSerializer;
import com.saasovation.identityaccess.IdentityAccessAppTest;
import com.saasovation.common.TestBase;
import com.saasovation.identityaccess.application.command.*;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.MediaType;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

@SpringBootTest(classes = IdentityAccessAppTest.class, webEnvironment = RANDOM_PORT)
@TestContainers(use = true)
public abstract class ResourceTestBase extends TestBase {
    protected static final String FIXTURE_GROUP_NAME = "Test Group";
    protected static final String FIXTURE_PASSWORD = "SecretPassword!";
    protected static final String FIXTURE_ROLE_NAME = "Test Role";
    protected static final String FIXTURE_TENANT_DESCRIPTION = "This is a test tenant.";
    protected static final String FIXTURE_TENANT_NAME = "Test Tenant";
    protected static final String FIXTURE_USER_EMAIL_ADDRESS = "jdoe@saasovation.com";
    protected static final String FIXTURE_USER_EMAIL_ADDRESS2 = "zdoe@saasovation.com";
    protected static final String FIXTURE_USERNAME = "jdoe";
    protected static final String FIXTURE_USERNAME2 = "zdoe";

    @DynamicPropertySource
    public static void dataSourceProperties(DynamicPropertyRegistry registry) {
        if (!useTestContainers(ResourceTestBase.class)) {
            return;
        }
        initMySqlContainer(registry);
        initRabbitMqContainer(registry);
    }

    @Autowired
    protected EventStore eventStore;
    @LocalServerPort
    protected int port;
    protected WebTestClient client;

    public ResourceTestBase() {
        super();
    }

    @BeforeEach
    public void beforeEach() {
        DomainEventPublisher.instance().reset();
        client = WebTestClient.bindToServer()
                .baseUrl("http://localhost:" + port)
                .build();
    }

    protected String provisionTenant(ProvisionTenantCommand tenantCommand) {
        String commandJson = ObjectSerializer.instance().serialize(tenantCommand);
        FluxExchangeResult<String> result = client.post().uri("/tenants")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk()
                .returnResult(String.class);
        byte[] body = result.getResponseBodyContent();
        assertNotNull(body);
        String resultString = new String(body, StandardCharsets.UTF_8);
        RepresentationReader reader = new RepresentationReader(resultString);
        return reader.stringValue("tenantId");
    }

    protected String provisionGroup(ProvisionGroupCommand groupCommand) {
        String commandJson = ObjectSerializer.instance().serialize(groupCommand);
        FluxExchangeResult<String> result = client.post()
                .uri(String.format("/tenants/%s/groups", groupCommand.getTenantId()))
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk()
                .returnResult(String.class);
        byte[] body = result.getResponseBodyContent();
        assertNotNull(body);
        return new String(body, StandardCharsets.UTF_8);
    }

    protected String provisionRole(ProvisionRoleCommand roleCommand) {
        String commandJson = ObjectSerializer.instance().serialize(roleCommand);
        FluxExchangeResult<String> result = client.post()
                .uri(String.format("/tenants/%s/roles", roleCommand.getTenantId()))
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk()
                .returnResult(String.class);
        byte[] body = result.getResponseBodyContent();
        assertNotNull(body);
        return new String(body, StandardCharsets.UTF_8);
    }

    protected void assignUserToRole(String tenantId, String roleName, String username) {
        AssignUserToRoleCommand command = new AssignUserToRoleCommand();
        command.setTenantId(tenantId);
        command.setRoleName(roleName);
        command.setUsername(username);
        String commandJson = ObjectSerializer.instance().serialize(command);
        FluxExchangeResult<String> result = client.post()
                .uri(String.format("/tenants/%s/roles/%s/users",
                        command.getTenantId(), command.getRoleName()))
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk()
                .returnResult(String.class);
        byte[] body = result.getResponseBodyContent();
        assertTrue(body == null || body.length == 0);
    }

    protected String offerInvitation(String tenantId, String description) {
        OfferRegistrationInvitationCommand command = new OfferRegistrationInvitationCommand();
        command.setTenantId(tenantId);
        command.setDescription(description);
        String commandJson = ObjectSerializer.instance().serialize(command);
        FluxExchangeResult<String> result = client.post()
                .uri("/tenants/invitations")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk()
                .returnResult(String.class);
        byte[] body = result.getResponseBodyContent();
        assertNotNull(body);
        return new String(body, StandardCharsets.UTF_8);
    }

    protected String registerUser(RegisterUserCommand userCommand) {
        String tenantId = userCommand.getTenantId();
        String description = "this is a test invitation:" + UUID.randomUUID();
        String invitationId = this.offerInvitation(tenantId, description);
        userCommand.setInvitationIdentifier(invitationId);
        String commandJson = ObjectSerializer.instance().serialize(userCommand);
        FluxExchangeResult<String> result = client.post()
                .uri(String.format("/tenants/%s/users", tenantId))
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk()
                .returnResult(String.class);
        byte[] body = result.getResponseBodyContent();
        assertNotNull(body);
        String resultString = new String(body, StandardCharsets.UTF_8);
        RepresentationReader reader = new RepresentationReader(resultString);
        return reader.stringValue("username");
    }

    protected void updateUserEmail(String tenantId, String username, String email) {
        ChangeEmailAddressCommand command = new ChangeEmailAddressCommand();
        command.setTenantId(tenantId);
        command.setUsername(username);
        command.setEmailAddress(email);
        String commandJson = ObjectSerializer.instance().serialize(command);
        client.put()
                .uri(String.format("/tenants/%s/users/%s/email", tenantId, username))
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(commandJson)
                .exchange()
                .expectStatus()
                .isOk();
    }

    protected ProvisionTenantCommand provisionTenantCommand() {
        return new ProvisionTenantCommand(
                FIXTURE_TENANT_NAME + ":" + UUID.randomUUID(),
                FIXTURE_TENANT_DESCRIPTION,
                "John", "Doe",
                FIXTURE_USER_EMAIL_ADDRESS,
                "303-555-1210",
                "303-555-1212",
                "123 Pearl Street",
                "Boulder",
                "CO",
                "80301",
                "US");
    }

    protected ProvisionGroupCommand provisionGroupCommand(String tenantId) {
        return new ProvisionGroupCommand(tenantId,
                FIXTURE_GROUP_NAME + ":" + UUID.randomUUID(),
                "This is test group");
    }

    protected ProvisionRoleCommand provisionRoleCommand(String tenantId) {
        return new ProvisionRoleCommand(tenantId,
                FIXTURE_ROLE_NAME + ":" + UUID.randomUUID(),
                "This is test role",
                true
        );
    }

    protected RegisterUserCommand registerUserCommand(String tenantId) {
        return new RegisterUserCommand(tenantId,
                null,
                "jdoe:" + UUID.randomUUID(),
                FIXTURE_PASSWORD,
                "John",
                "Doe",
                true,
                null,
                null,
                FIXTURE_USER_EMAIL_ADDRESS,
                "303-555-1210",
                "303-555-1212",
                "123 Pearl Street",
                "Boulder",
                "CO",
                "80301",
                "US"
        );
    }
}
